package ru.artyushov.fingers.core.preprocessor;

import ru.artyushov.fingers.core.Preprocessor;
import ru.artyushov.fingers.core.data.Fingerprint;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;

public class PatternBasedSkeletizer implements Preprocessor {

    private static final Collection<CellConstraint[][]> PATTERNS;
    private static final Collection<CellConstraint[][]> NOISE_REMOVERS;

    private static enum CellConstraint {TRUE, FALSE, WHO_CARES}

    static {
        final Collection<CellConstraint[][]> patterns = new LinkedList<CellConstraint[][]>();

        CellConstraint[][] pattern1 = {
                {CellConstraint.WHO_CARES, CellConstraint.FALSE, CellConstraint.FALSE},
                {CellConstraint.TRUE, CellConstraint.TRUE, CellConstraint.FALSE},
                {CellConstraint.WHO_CARES, CellConstraint.TRUE, CellConstraint.WHO_CARES},
        };

        CellConstraint[][] pattern2 = {
                {CellConstraint.FALSE, CellConstraint.FALSE, CellConstraint.FALSE},
                {CellConstraint.TRUE, CellConstraint.TRUE, CellConstraint.TRUE},
                {CellConstraint.WHO_CARES, CellConstraint.TRUE, CellConstraint.WHO_CARES},
        };

        patterns.addAll(getAllRotations(pattern1));
        patterns.addAll(getAllRotations(pattern2));

        PATTERNS = Collections.unmodifiableCollection(patterns);
    }

    static {
        final Collection<CellConstraint[][]> noiseRemovers = new LinkedList<CellConstraint[][]>();

        CellConstraint[][] foreverAlone = {
                {CellConstraint.FALSE, CellConstraint.FALSE, CellConstraint.FALSE},
                {CellConstraint.FALSE, CellConstraint.TRUE, CellConstraint.FALSE},
                {CellConstraint.FALSE, CellConstraint.FALSE, CellConstraint.FALSE},
        };

        CellConstraint[][] pattern2 = {
                {CellConstraint.FALSE, CellConstraint.FALSE, CellConstraint.FALSE},
                {CellConstraint.FALSE, CellConstraint.TRUE, CellConstraint.FALSE},
                {CellConstraint.TRUE, CellConstraint.TRUE, CellConstraint.TRUE},
        };

        CellConstraint[][] pattern3 = {
                {CellConstraint.FALSE, CellConstraint.FALSE, CellConstraint.FALSE},
                {CellConstraint.FALSE, CellConstraint.TRUE, CellConstraint.FALSE},
                {CellConstraint.TRUE, CellConstraint.TRUE, CellConstraint.FALSE},
        };

        CellConstraint[][] pattern4 = {
                {CellConstraint.FALSE, CellConstraint.FALSE, CellConstraint.FALSE},
                {CellConstraint.FALSE, CellConstraint.TRUE, CellConstraint.FALSE},
                {CellConstraint.FALSE, CellConstraint.TRUE, CellConstraint.TRUE},
        };

        noiseRemovers.add(foreverAlone);
        noiseRemovers.addAll(getAllRotations(pattern2));
        noiseRemovers.addAll(getAllRotations(pattern3));
        noiseRemovers.addAll(getAllRotations(pattern4));

        NOISE_REMOVERS = Collections.unmodifiableCollection(noiseRemovers);
    }

    private static Collection<CellConstraint[][]> getAllRotations(CellConstraint[][] pattern) {
        Collection<CellConstraint[][]> result = new LinkedList<CellConstraint[][]>();

        result.add(pattern);

        for(int i = 0; i < 3; i ++) {
            pattern = rotated(pattern);
            result.add(pattern);
        }

        return result;
    }

    private static CellConstraint[][] rotated(CellConstraint[][] toRotate) {

        int sideLength = toRotate.length;

        CellConstraint[][] result = new CellConstraint[sideLength][sideLength];


        for (int i = 0; i < sideLength; ++i) {
            for (int j = 0; j < sideLength; ++j) {
                result[i][j] = toRotate[sideLength - j - 1][i];
            }
        }

        return result;
    }


    @Override
    public Fingerprint workYourMagic(Fingerprint on) {
        final boolean[][] data = on.getData();

        while(cleanupPatterns(data, on.getRowNumber())) {
            cleanupNoise(data, on.getRowNumber());
        }

        return new Fingerprint(data, on.getRowNumber());
    }

    private boolean cleanupPatterns(boolean[][] data, int rowNumber) {
        return cleanup(PATTERNS, data, rowNumber);
    }

    private boolean cleanupNoise(boolean[][] data, int rowNumber) {
        return cleanup(NOISE_REMOVERS, data, rowNumber);
    }

    private boolean cleanup(Collection<CellConstraint[][]> patterns, boolean[][] data, int rowNumber) {
        boolean removedSomething = false;

        for(int col = 1; col < data.length - 1; col ++) {
            for(int row = 1; row < rowNumber - 1; row ++) {
                for(CellConstraint[][] pattern : patterns) {
                    if(matches(data, col, row, pattern)) {
                        removedSomething = true;
                        data[col][row] = false;
                    }
                }
            }
        }

        return removedSomething;
    }

    private boolean matches(boolean[][] data, int col, int row, CellConstraint[][] pattern) {
        //FIXME: dirty: we assume that the patterns are always 3x3

        for(int colOffset = 0; colOffset < 3; colOffset++) {
            for(int rowOffset = 0; rowOffset < 3; rowOffset++) {
                boolean realValue = data[col + colOffset - 1][row + rowOffset - 1];
                CellConstraint expectedValue = pattern[colOffset][rowOffset];

                if(expectedValue == CellConstraint.FALSE && realValue) return false;
                if(expectedValue == CellConstraint.TRUE && !realValue) return false;
            }
        }
        
        return true;

    }


}
